package controller

import (
	"context"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/dynamic/dynamicinformer"
	"k8s.io/client-go/restmapper"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"
	"operator/pkg/format"
	"operator/pkg/types/egress"
	"operator/pkg/types/item"
	"operator/pkg/types/unstruct"
	"strings"
	"sync"
	"time"
)

type EgressController struct {
	ctx         context.Context
	mu          sync.RWMutex
	client      dynamic.Interface
	workerCount int
	maxRetry    int
	informer    dynamicinformer.DynamicSharedInformerFactory
	queue       workqueue.RateLimitingInterface
	mapper      *restmapper.DeferredDiscoveryRESTMapper
	updateCh    chan<- []byte
	egressList  cache.GenericLister
	ipList      map[string]struct{}
}

func (e *EgressController) Run(stopCh chan struct{}) {
	for i := 0; i < e.workerCount; i++ {
		go wait.Until(e.worker, time.Minute, stopCh)
	}
}

func (e *EgressController) worker() {
	for e.processNextItem() {
	}
}

func (e *EgressController) processNextItem() bool {
	it, shutdown := e.queue.Get()
	if shutdown {
		return false
	}
	defer e.queue.Done(it)

	err := e.syncEgress(it.(item.ItemOperation))
	if err != nil {
		format.Errors("pkg/controller/egress.go", 56, err.Error())
		e.handlerError(it.(item.ItemOperation))
		return false
	}
	return true
}

func (e *EgressController) syncEgress(it item.ItemOperation) error {
	var op byte
	switch it.Operation {
	case item.ADD:
		eg := &egress.Egress{}
		err := unstruct.UnstructToObject(it.Object.(*unstructured.Unstructured), eg)
		if err != nil {
			return err
		}

		e.mu.Lock()
		defer e.mu.Unlock()
		for _, i := range eg.Spec.Resources {
			e.ipList[i] = struct{}{}
		}
		op = 1
	case item.DEL:
		eg := &egress.Egress{}
		err := unstruct.UnstructToObject(it.Object.(*unstructured.Unstructured), eg)
		if err != nil {
			return err
		}

		e.mu.Lock()
		defer e.mu.Unlock()
		for _, i := range eg.Spec.Resources {
			delete(e.ipList, i)
		}
		op = 0
	default:
		return nil
	}

	result := make([]string, 0, 10)
	for k, _ := range e.ipList {
		result = append(result, k)
	}
	e.updateCh <- append([]byte{op}, []byte(strings.Join(result, ","))...)

	return nil
}

func (e *EgressController) handlerError(it item.ItemOperation) {
	if e.queue.NumRequeues(it) <= e.maxRetry {
		e.queue.AddRateLimited(it)
		return
	}

	e.queue.Forget(it)
}

func (e *EgressController) egressGVR() *schema.GroupVersionResource {
	gvk := schema.GroupVersionKind{}
	gvk.Version = "v1"
	gvk.Group = "king.io"
	gvk.Kind = "egress"

	restmapper, err := e.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
	if err != nil {
		return nil
	}

	return &restmapper.Resource
}

func (e *EgressController) addEgress(obj interface{}) {
	ms, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
	if err != nil {
		return
	}
	op := item.ItemOperation{
		Operation: item.ADD,
		Item:      ms,
		Object:    obj,
	}
	e.queue.Add(op)
}

func (e *EgressController) updateEgress(old, new interface{}) {
	op := item.ItemOperation{
		Operation: item.ADD,
		Object:    new,
	}

	ms, err := cache.DeletionHandlingMetaNamespaceKeyFunc(new)
	if err != nil {
		return
	}
	op.Item = ms

	e.queue.Add(op)
}

func (e *EgressController) delEgress(obj interface{}) {
	ms, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
	if err != nil {
		return
	}
	op := item.ItemOperation{
		Operation: item.DEL,
		Item:      ms,
		Object:    obj,
	}

	e.queue.Add(op)
}

func NewEgressController(ctx context.Context,
	dSharedInformer dynamicinformer.DynamicSharedInformerFactory,
	mapper *restmapper.DeferredDiscoveryRESTMapper,
	client dynamic.Interface,
	workerCount int,
	maxRetry int,
	updateCh chan<- []byte,
) *EgressController {
	e := &EgressController{
		ctx:         ctx,
		mu:          sync.RWMutex{},
		client:      client,
		workerCount: workerCount,
		maxRetry:    maxRetry,
		mapper:      mapper,
		informer:    dSharedInformer,
		updateCh:    updateCh,
		ipList:      map[string]struct{}{},
		queue:       workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "egressController"),
	}

	e.egressList = dSharedInformer.ForResource(*e.egressGVR()).Lister()

	dSharedInformer.ForResource(*e.egressGVR()).Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc:    e.addEgress,
		UpdateFunc: e.updateEgress,
		DeleteFunc: e.delEgress,
	})

	return e
}
